/*
 * Copyright (c) 2009. Silenus Consultoria, S.L.
 */
package es.silenus.detecta.dao.impl;

import es.silenus.detecta.dao.CouncilGroupDAO;
import es.silenus.detecta.dao.Page;

import es.silenus.detecta.model.Council;
import es.silenus.detecta.model.CouncilGroup;

import org.hibernate.FetchMode;
import org.hibernate.Hibernate;
import org.hibernate.Session;

import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;

import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import static java.util.Arrays.asList;
import java.util.List;
import java.util.Map;


/**
 * Council group DAO implementation.
 *
 * @author <a href="mailto:malonso@silenus-consultoria.es">Mariano Alonso</a>
 *
 * @since 08-may-2009 11:51:56
 */
public class CouncilGroupDAOImpl extends ReadWriteDAOImpl<CouncilGroup, Long> implements CouncilGroupDAO {
	/**
	 * The check remove councils groups SQL.
	 */
	private String checkRemoveCouncilgroupsSQL;

	/**
	 * Sets the check remove councils groups SQL.
	 *
	 * @param checkRemoveCouncilgroupsSQL the check remove councils groups SQL.
	 */
	public void setCheckRemoveCouncilgroupsSQL(String checkRemoveCouncilgroupsSQL) {
		this.checkRemoveCouncilgroupsSQL = checkRemoveCouncilgroupsSQL;
	}

	/**
	 * Finds all council groups an organization owns.
	 *
	 * @param organizationId the organization id.
	 *
	 * @return the list of council groups.
	 */
	@SuppressWarnings({"unchecked"})
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<CouncilGroup> findByOrganization(int organizationId) {
		Session session = null;

		try {
			session = getSession();

			return (List<CouncilGroup>)session.getNamedQuery(CouncilGroup.class.getName() + ".findByOrganization")
																				.setInteger("organizationId", organizationId)
																				.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY).list();
		} finally {
			releaseSession(session);
		}
	}

	/**
	 * Finds a council group given its code and organization id.
	 *
	 * @param name the name.
	 * @param organizationId the organization id.
	 *
	 * @return the council group.
	 */
	@SuppressWarnings({"unchecked"})
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public CouncilGroup findByName(String name, int organizationId) {
		Session session = null;

		try {
			session = getSession();

			return (CouncilGroup)session.getNamedQuery(CouncilGroup.class.getName() + ".findByName").setString("name", name)
																	.setInteger("organizationId", organizationId).uniqueResult();
		} finally {
			releaseSession(session);
		}
	}

	/**
	 * Override this method to provide a find by example page criteria.
	 *
	 * @param criteria the criteria.
	 * @param object the sample object.
	 *
	 * @return the criteria.
	 */
	protected DetachedCriteria preparePageCriteria(final DetachedCriteria criteria, final Map<String, Object> object) {
		criteria.setFetchMode("organization", FetchMode.JOIN);

		Object temp = object.get("id");

		if(temp != null) {
			criteria.add(Restrictions.eq("id", temp));
		}

		temp = object.get("name");

		if(temp != null) {
			criteria.add(Restrictions.ilike("name", (String)temp, MatchMode.ANYWHERE));
		}

		temp = object.get("organization.id");

		if(temp != null) {
			criteria.add(Restrictions.eq("organization.id", temp));
		}

		return criteria;
	}

	/**
	 * Override this method to provide a find by example list criteria.
	 *
	 * @param criteria the criteria.
	 * @param object the sample object.
	 *
	 * @return the criteria.
	 */
	protected DetachedCriteria prepareListCriteria(final DetachedCriteria criteria, final Map<String, Object> object) {
		return preparePageCriteria(criteria, object);
	}

	/**
	 * Finds councils associated with the given criteria.
	 *
	 * @param sample the sample criteria.
	 * @param start the start index.
	 * @param limit the limit index.
	 * @param sortFields the sort fields.
	 * @param sortDirections the sort directions.
	 *
	 * @return the page.
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public Page<Council> findAssociatedCouncils(
		final Map<String, Object> sample, int start, int limit, final String[] sortFields, final String[] sortDirections) {
		DetachedCriteria criteria = DetachedCriteria.forEntityName("Council");

		criteria.setFetchMode("province", FetchMode.JOIN);

		DetachedCriteria provinceCriteria = criteria.createCriteria("province");

		// Prepare the page criteria
		criteria.add(
			Restrictions.sqlRestriction(
				"EXISTS (SELECT 1 FROM council_group_members a WHERE a.id_council={alias}.id_council AND a.id_group=?)",
				sample.get("id"), Hibernate.LONG));

		Object temp = sample.get("organization.id");

		if(temp != null) {
			criteria.add(
				Restrictions.sqlRestriction(
					"EXISTS (SELECT 1 FROM organization_provinces a WHERE a.id_province={alias}.id_province AND a.id_organization=?)",
					temp, Hibernate.INTEGER));
		}


		// Build page and paginate
		PageImpl<Council> page = new PageImpl<Council>(getSessionFactory());

		page.setStart(start);
		page.setLimit(limit);
		page.setCriteria(criteria);

		if((sortFields != null) && (sortDirections != null) && (sortFields.length == sortDirections.length)) {
			for(int i = 0; i < sortFields.length; ++i) {
				if("province".equals(sortFields[i])) {
					if("asc".equalsIgnoreCase(sortDirections[i])) {
						provinceCriteria.addOrder(Order.asc("name"));
					} else {
						provinceCriteria.addOrder(Order.desc("name"));
					}
				} else {
					page.addSort(sortFields[i], sortDirections[i]);
				}
			}
		}

		page.paginate();

		return page;
	}

	/**
	 * Finds councils non associated with the given criteria.
	 *
	 * @param sample the sample criteria.
	 * @param start the start index.
	 * @param limit the limit index.
	 * @param sortFields the sort fields.
	 * @param sortDirections the sort directions.
	 *
	 * @return the page.
	 */
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public Page<Council> findNonAssociatedCouncils(
		final Map<String, Object> sample, int start, int limit, final String[] sortFields, final String[] sortDirections) {
		DetachedCriteria criteria = DetachedCriteria.forEntityName("Council");

		criteria.setFetchMode("province", FetchMode.JOIN);

		DetachedCriteria provinceCriteria = criteria.createCriteria("province");

		// Prepare the page criteria
		criteria.add(
			Restrictions.sqlRestriction(
				"NOT EXISTS (SELECT 1 FROM council_group_members a WHERE a.id_council={alias}.id_council AND a.id_group=?)",
				sample.get("id"), Hibernate.LONG));

		Object temp = sample.get("organization.id");

		if(temp != null) {
			criteria.add(
				Restrictions.sqlRestriction(
					"EXISTS (SELECT 1 FROM organization_provinces a WHERE a.id_province={alias}.id_province AND a.id_organization=?)",
					temp, Hibernate.INTEGER));
		}


		// Build page and paginate
		PageImpl<Council> page = new PageImpl<Council>(getSessionFactory());

		page.setStart(start);
		page.setLimit(limit);
		page.setCriteria(criteria);

		if((sortFields != null) && (sortDirections != null) && (sortFields.length == sortDirections.length)) {
			for(int i = 0; i < sortFields.length; ++i) {
				if("province".equals(sortFields[i])) {
					if("asc".equalsIgnoreCase(sortDirections[i])) {
						provinceCriteria.addOrder(Order.asc("name"));
					} else {
						provinceCriteria.addOrder(Order.desc("name"));
					}
				} else {
					page.addSort(sortFields[i], sortDirections[i]);
				}
			}
		}

		page.paginate();

		return page;
	}

	/**
	 * Retrieves the removal information of entities with the given keys.
	 *
	 * @param keys the keys.
	 *
	 * @return the information requested.
	 */
	@SuppressWarnings({"unchecked"})
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<Map<String, Object>> getRemovalInformation(final Long[] keys) {
		MapSqlParameterSource args = new MapSqlParameterSource();

		args.addValue("ids", asList(keys));

		return jdbcTemplate.queryForList(checkRemoveCouncilgroupsSQL, args);
	}

	/**
	 * Removes councils groups by keys.
	 *
	 * @param keys the keys.
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void remove(final Long[] keys) {
		List<CouncilGroup> elements = findByIds(keys);

		for(CouncilGroup element : elements) {
			super.remove(element);
		}
	}
}
